// 2024/1/15: 首个版本
// 2024/8/16: 增加ConnectTimeout
// 2024/8/16: 修正被动断开后无法连接问题
// 2024/8/16: 使用更完善的连接超时机制
// 2024/8/19: 降低传输延迟
// 2024/8/20: 接收端检查心跳。新增IsAcceptFailed
// 2024/8/22: 捕获设置SendTimeout异常
// 2024/8/22: 修正接收数据漏帧问题
// 2024/8/26: 增加ConnectTimeout至600ms
// 2024/8/29: 剥离负责实际传输的代码
// 2025/7/3: 新增SendPacket.GetActualPacketSize
// 2025/7/22: 新增ReceiveStream.VersionMismatch
// 2025/7/23: 通讯协议版本v2
// 2025/8/19: 新增ReceiveStream.Parse(webSocketMode)。新增ReceiveStream.Reset

using System;
using System.Collections.Generic;
using System.Linq;
using ASEva;

namespace RemoteConsole
{
    class PacketMagic
    {
        public static byte[] Head = [ 55, 13, 34, 8, 233, 144, 22 ];
    }

    class Masks
    {
        public Masks()
        {
            var random = new CompatibleRandom(PacketMagic.Head.Last());
            for (int i = 0; i < 256; i++)
            {
                masks[i] = new byte[2048];
                if (i == 255) random.NextBytes(masks[i]);
            }
        }

        public void UpdateSeed(int maskSeed)
        {
            if (seedUpdated) return;
            var random = new CompatibleRandom(maskSeed);
            for (int i = 0; i < 255; i++)
            {
                random.NextBytes(masks[i]);
            }
            seedUpdated = true;
        }

        public byte AddMask(Span<byte> target, byte maskID)
        {
            if (!seedUpdated) maskID = 255;
            var mask = masks[maskID];
            var maskIndex = 0;
            for (int i = 0; i < target.Length; i++)
            {
                target[i] = (byte)(target[i] ^ mask[maskIndex++]);
                if (maskIndex == mask.Length) maskIndex = 0;
            }
            return maskID;
        }

        public bool RemoveMask(Span<byte> target, byte maskID)
        {
            if (!seedUpdated && maskID != 255) return false;
            var mask = masks[maskID];
            var maskIndex = 0;
            for (int i = 0; i < target.Length; i++)
            {
                target[i] = (byte)(target[i] ^ mask[maskIndex++]);
                if (maskIndex == mask.Length) maskIndex = 0;
            }
            return true;
        }

        private class CompatibleRandom
        {
            public CompatibleRandom(int seed)
            {
                int num = (seed == int.MinValue) ? int.MaxValue : Math.Abs(seed);
                seedArray[55] = num;
                int num3 = 1;
                for (int i = 1; i < 55; i++)
                {
                    int num4 = 21 * i % 55;
                    seedArray[num4] = num3;
                    num3 = num - num3;
                    if (num3 < 0)
                    {
                        num3 += int.MaxValue;
                    }
                    num = seedArray[num4];
                }
                for (int j = 1; j < 5; j++)
                {
                    for (int k = 1; k < 56; k++)
                    {
                        seedArray[k] -= seedArray[1 + (k + 30) % 55];
                        if (seedArray[k] < 0)
                        {
                            seedArray[k] += int.MaxValue;
                        }
                    }
                }
                inext = 0;
                inextp = 21;
            }

            public int Next()
            {
                int num = inext;
                int num2 = inextp;
                if (++num >= 56)
                {
                    num = 1;
                }
                if (++num2 >= 56)
                {
                    num2 = 1;
                }
                int num3 = seedArray[num] - seedArray[num2];
                if (num3 == int.MaxValue)
                {
                    num3--;
                }
                if (num3 < 0)
                {
                    num3 += int.MaxValue;
                }
                seedArray[num] = num3;
                inext = num;
                inextp = num2;
                return num3;
            }

            public void NextBytes(byte[] buffer)
            {
                for (int i = 0; i < buffer.Length; i++)
                {
                    buffer[i] = (byte)(Next() & 0xFF);
                }
            }

            private int inext;
            private int inextp;
            private int[] seedArray = new int[56];
        }

        private byte[][] masks = new byte[256][];
        private bool seedUpdated = false;
    }

    class ReceiveStream(Masks masks)
    {
        public void Append(byte[] received)
        {
            bufferList.Add(received);
        }

        public byte[][] Parse(bool webSocketMode = false)
        {
            List<List<byte>> packetList = new List<List<byte>>();
            List<byte> curPacket = null;
            int magicIndex = 0;
            foreach (var buffer in bufferList)
            {
                for (int i = 0; i < buffer.Length; i++)
                {
                    if (curPacket != null) curPacket.Add(buffer[i]);
                    if (buffer[i] != PacketMagic.Head[magicIndex])
                    {
                        if (magicIndex == 6 && buffer[i] >= 21 && buffer[i] < 30 && !VersionMismatch)
                        {
                            AgencyLocal.Log(AgencyLocal.GetAppLanguage() == Language.Chinese ? "通讯协议版本不匹配。" : "Communication protocol version mismatch.", LogLevel.Warning);
                            VersionMismatch = true;
                        }
                        magicIndex = 0;
                    }
                    if (buffer[i] == PacketMagic.Head[magicIndex] && ++magicIndex == 7)
                    {
                        magicIndex = 0;
                        if (curPacket != null)
                        {
                            curPacket.RemoveRange(curPacket.Count - 7, 7);
                            if (curPacket.Count >= 12 && curPacket.Count % 4 == 0) packetList.Add(curPacket);
                        }
                        curPacket = new List<byte>();
                        curPacket.AddRange(PacketMagic.Head);
                    }
                }
            }

            bufferList.Clear();
            if (curPacket != null)
            {
                if (webSocketMode) packetList.Add(curPacket);
                else bufferList.Add(curPacket.ToArray());
            }

            var outputs = new List<byte[]>();
            foreach (var packet in packetList)
            {
                var maskID = packet[7];

                var header = new byte[4];
                packet.CopyTo(8, header, 0, 4);
                if (!masks.RemoveMask(header, maskID)) continue;

                var binarySize = ((int)header[1] << 16) | ((int)header[2] << 8) | header[3];
                if (binarySize < 4) continue;

                var fullSize = SendPacket.GetActualPacketSize(binarySize);
                if (packet.Count != fullSize) continue;

                if (header[0] == 255) continue; // remove heart beat

                var data = new byte[binarySize];
                packet.CopyTo(8, data, 0, binarySize);
                if (!masks.RemoveMask(data, maskID)) continue;
                
                data[1] = data[2] = data[3] = 0; // erase binary size

                outputs.Add(data);

                if (maskID != 255) RandomMaskReceived = true;
            }
            return outputs.ToArray();
        }

        public void Reset()
        {
            VersionMismatch = false;
            RandomMaskReceived = false;
            bufferList.Clear();
        }

        public bool VersionMismatch { get; private set; } = false;
        public bool RandomMaskReceived { get; private set; } = false;

        private List<byte[]> bufferList = new List<byte[]>();
    }

    class SendPacket(Masks masks)
    {
        public static int GetActualPacketSize(int dataSize)
        {
            var alignBytes = dataSize % 4 == 0 ? 0 : (4 - dataSize % 4);
            return 8 + dataSize + alignBytes;
        }

        public byte[] Generate(byte[] data, bool randomMask)
        {
            if (data.Length < 4) return null;
            if (data.Length >= 65536 * 256) return null;
            if (data[1] != 0 || data[2] != 0 || data[3] != 0) return null;

            var fullBinary = new byte[GetActualPacketSize(data.Length)];
            Array.Copy(PacketMagic.Head, 0, fullBinary, 0, 7);
            Array.Copy(data, 0, fullBinary, 8, data.Length);
            fullBinary[9] = (byte)(data.Length >> 16);
            fullBinary[10] = (byte)((data.Length >> 8) & 0xFF);
            fullBinary[11] = (byte)(data.Length & 0xFF);

            var maskID = (byte)(randomMask ? (random.NextInt64() % 255) : 255);
            fullBinary[7] = masks.AddMask(fullBinary.AsSpan(8, data.Length), maskID);
            return fullBinary;
        }

        public byte[] GenerateHeartBeat()
        {
            byte maskID = 255;

            var heartBeatBinary = new byte[12];
            Array.Copy(PacketMagic.Head, 0, heartBeatBinary, 0, 7);
            heartBeatBinary[7] = maskID;
            heartBeatBinary[8] = 255; // Protocol.HeartBeat
            heartBeatBinary[11] = 4;

            masks.AddMask(heartBeatBinary.AsSpan(8, 4), maskID);
            return heartBeatBinary;
        }

        private Random random = new Random(DateTime.Now.ToString().GetHashCode());
    }
}